

use std::ffi::CStr;
use std::os::raw::{c_char, c_void};
use std::ptr;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Mutex;
const LTEST_FLOAT_TOLERANCE: f64 = 0.001;
macro_rules! assert {
    ($ expr : expr) => {
        num_assert.fetch_add(1, Ordering::SeqCst);
        if !$expr {
            eprintln!(
                "{}:{}: in {}: assertion `{}` failed",
                file!(),
                line!(),
                &*format!("{}", module_path!()),
                stringify!($expr)
            );
            panic!();
        }
    };
}
macro_rules! assert_float_equal {
    ($ a : expr , $ b : expr) => {
        let compare = (f64::from($a) - f64::from($b)).abs();
        if compare > LTEST_FLOAT_TOLERANCE || compare.is_nan() {
            println!(
                "{}:{} ({} != {})",
                file!(),
                line!(),
                f64::from($a),
                f64::from($b)
            );
            assert!(false);
        }
    };
}
macro_rules! cross_check {
    ($ a : expr , $ b : expr) => {
        if ($b).is_nan() {
            break;
        }
        let expr = te_compile($a, lookup, 2, &mut err);
        assert_float_equal!(te_eval(&expr), $b);
        assert!(err == 0);
        te_free(expr);
    };
}
#[repr(C)]
struct te_expr {
    _type: i32,
    parameters: [*const c_void; 1],
}
#[repr(C)]
struct te_variable {
    name: *const c_char,
    address: *const c_void,
    _type: i32,
    context: *const c_void,
}
type UnitTestFunction = fn();
static tests: &[UnitTestFunction] = &[
    test_results,
    test_syntax,
    test_nans,
    test_infs,
    test_variables,
    test_functions,
    test_dynamic,
    test_closure,
    test_optimize,
    test_pow,
    test_combinatorics,
    { () },
];
static num_assert: AtomicUsize = AtomicUsize::new(0);
#[repr(C)]
struct TestCase {
    expr: *const c_char,
    answer: f64,
}
#[repr(C)]
struct TestEqu {
    expr1: *const c_char,
    expr2: *const c_char,
}


fn test_results() {
    let cases = vec![
        TestCase {
            expr: "1",
            answer: 1.0,
        },
        TestCase {
            expr: "1 ",
            answer: 1.0,
        },
        TestCase {
            expr: "(1)",
            answer: 1.0,
        },
        TestCase {
            expr: "pi",
            answer: 3.14159,
        },
        TestCase {
            expr: "atan(1)*4 - pi",
            answer: 0.0,
        },
        TestCase {
            expr: "e",
            answer: 2.71828,
        },
        TestCase {
            expr: "2+1",
            answer: 2.0 + 1.0,
        },
        TestCase {
            expr: "(((2+(1))))",
            answer: 2.0 + 1.0,
        },
        TestCase {
            expr: "3+2",
            answer: 3.0 + 2.0,
        },
        TestCase {
            expr: "3+2+4",
            answer: 3.0 + 2.0 + 4.0,
        },
        TestCase {
            expr: "(3+2)+4",
            answer: 3.0 + 2.0 + 4.0,
        },
        TestCase {
            expr: "3+(2+4)",
            answer: 3.0 + 2.0 + 4.0,
        },
        TestCase {
            expr: "(3+2+4)",
            answer: 3.0 + 2.0 + 4.0,
        },
        TestCase {
            expr: "3*2*4",
            answer: 3.0 * 2.0 * 4.0,
        },
        TestCase {
            expr: "(3*2)*4",
            answer: 3.0 * 2.0 * 4.0,
        },
        TestCase {
            expr: "3*(2*4)",
            answer: 3.0 * 2.0 * 4.0,
        },
        TestCase {
            expr: "(3*2*4)",
            answer: 3.0 * 2.0 * 4.0,
        },
        TestCase {
            expr: "3-2-4",
            answer: 3.0 - 2.0 - 4.0,
        },
        TestCase {
            expr: "(3-2)-4",
            answer: (3.0 - 2.0) - 4.0,
        },
        TestCase {
            expr: "3-(2-4)",
            answer: 3.0 - (2.0 - 4.0),
        },
        TestCase {
            expr: "(3-2-4)",
            answer: 3.0 - 2.0 - 4.0,
        },
        TestCase {
            expr: "3/2/4",
            answer: 3.0 / 2.0 / 4.0,
        },
        TestCase {
            expr: "(3/2)/4",
            answer: (3.0 / 2.0) / 4.0,
        },
        TestCase {
            expr: "3/(2/4)",
            answer: 3.0 / (2.0 / 4.0),
        },
        TestCase {
            expr: "(3/2/4)",
            answer: 3.0 / 2.0 / 4.0,
        },
        TestCase {
            expr: "(3*2/4)",
            answer: 3.0 * 2.0 / 4.0,
        },
        TestCase {
            expr: "(3/2*4)",
            answer: 3.0 / 2.0 * 4.0,
        },
        TestCase {
            expr: "3*(2/4)",
            answer: 3.0 * (2.0 / 4.0),
        },
        TestCase {
            expr: "asin sin .5",
            answer: 0.5,
        },
        TestCase {
            expr: "sin asin .5",
            answer: 0.5,
        },
        TestCase {
            expr: "ln exp .5",
            answer: 0.5,
        },
        TestCase {
            expr: "exp ln .5",
            answer: 0.5,
        },
        TestCase {
            expr: "asin sin-.5",
            answer: -0.5,
        },
        TestCase {
            expr: "asin sin-0.5",
            answer: -0.5,
        },
        TestCase {
            expr: "asin sin -0.5",
            answer: -0.5,
        },
        TestCase {
            expr: "asin (sin -0.5)",
            answer: -0.5,
        },
        TestCase {
            expr: "asin (sin (-0.5))",
            answer: -0.5,
        },
        TestCase {
            expr: "asin sin (-0.5)",
            answer: -0.5,
        },
        TestCase {
            expr: "(asin sin (-0.5))",
            answer: -0.5,
        },
        TestCase {
            expr: "log10 1000",
            answer: 3.0,
        },
        TestCase {
            expr: "log10 1e3",
            answer: 3.0,
        },
        TestCase {
            expr: "log10(1000)",
            answer: 3.0,
        },
        TestCase {
            expr: "log10(1e3)",
            answer: 3.0,
        },
        TestCase {
            expr: "log10 1.0e3",
            answer: 3.0,
        },
        TestCase {
            expr: "10^5*5e-5",
            answer: 5.0,
        },
        #[cfg(feature = "te_nat_log")]
        TestCase {
            expr: "log 1000",
            answer: 6.9078,
        },
        #[cfg(feature = "te_nat_log")]
        TestCase {
            expr: "log e",
            answer: 1.0,
        },
        #[cfg(feature = "te_nat_log")]
        TestCase {
            expr: "log (e^10)",
            answer: 10.0,
        },
        #[cfg(not(feature = "te_nat_log"))]
        TestCase {
            expr: "log 1000",
            answer: 3.0,
        },
        TestCase {
            expr: "ln (e^10)",
            answer: 10.0,
        },
        TestCase {
            expr: "100^.5+1",
            answer: 11.0,
        },
        TestCase {
            expr: "100 ^.5+1",
            answer: 11.0,
        },
        TestCase {
            expr: "100^+.5+1",
            answer: 11.0,
        },
        TestCase {
            expr: "100^--.5+1",
            answer: 11.0,
        },
        TestCase {
            expr: "100^---+-++---++-+-+-.5+1",
            answer: 11.0,
        },
        TestCase {
            expr: "100^-.5+1",
            answer: 1.1,
        },
        TestCase {
            expr: "100^---.5+1",
            answer: 1.1,
        },
        TestCase {
            expr: "100^+---.5+1",
            answer: 1.1,
        },
        TestCase {
            expr: "1e2^+---.5e0+1e0",
            answer: 1.1,
        },
        TestCase {
            expr: "--(1e2^(+(-(-(-.5e0))))+1e0)",
            answer: 1.1,
        },
        TestCase {
            expr: "sqrt 100 + 7",
            answer: 17.0,
        },
        TestCase {
            expr: "sqrt 100 * 7",
            answer: 70.0,
        },
        TestCase {
            expr: "sqrt (100 * 100)",
            answer: 100.0,
        },
        TestCase {
            expr: "1,2",
            answer: 2.0,
        },
        TestCase {
            expr: "1,2+1",
            answer: 3.0,
        },
        TestCase {
            expr: "1+1,2+2,2+1",
            answer: 3.0,
        },
        TestCase {
            expr: "1,2,3",
            answer: 3.0,
        },
        TestCase {
            expr: "(1,2),3",
            answer: 3.0,
        },
        TestCase {
            expr: "1,(2,3)",
            answer: 3.0,
        },
        TestCase {
            expr: "-(1,(2,3))",
            answer: -3.0,
        },
        TestCase {
            expr: "2^2",
            answer: 4.0,
        },
        TestCase {
            expr: "pow(2,2)",
            answer: 4.0,
        },
        TestCase {
            expr: "atan2(1,1)",
            answer: 0.7854,
        },
        TestCase {
            expr: "atan2(1,2)",
            answer: 0.4636,
        },
        TestCase {
            expr: "atan2(2,1)",
            answer: 1.1071,
        },
        TestCase {
            expr: "atan2(3,4)",
            answer: 0.6435,
        },
        TestCase {
            expr: "atan2(3+3,4*2)",
            answer: 0.6435,
        },
        TestCase {
            expr: "atan2(3+3,(4*2))",
            answer: 0.6435,
        },
        TestCase {
            expr: "atan2((3+3),4*2)",
            answer: 0.6435,
        },
        TestCase {
            expr: "atan2((3+3),(4*2))",
            answer: 0.6435,
        },
    ];
    for case in cases {
        let expr = case.expr;
        let answer = case.answer;
        let mut err = 0;
        let ev = te_interp(expr, &mut err);
        assert_eq!(err, 0);
        assert!((ev - answer).abs() < f64::EPSILON);
        if err != 0 {
            println!("FAILED: {} ({})", expr, err);
        }
    }
}



 fn main() {
     let numbers = vec![
         1, 2, 3, 4, 5,
     ];
     let sum: i32 = numbers.iter().sum();
     println!("Sum is: {}", sum);
     
     let squares: Vec<i32> = numbers.iter().map(|&x| x * x).collect();
     println!("Squared numbers: {:?}", squares);
   
     let even_numbers: Vec<&i32> = numbers.iter().filter(|&x| x % 2 == 0).collect();
     println!("Even numbers: {:?}", even_numbers);
            answer: 2,
        },
        test_case {
            expr: "(1",
            answer: 2,
        },
        test_case {
            expr: "1**1",
            answer: 3,
        },
        test_case {
            expr: "1*2(+4",
            answer: 4,
        },
        test_case {
            expr: "1*2(1+4",
            answer: 4,
        },
        test_case {
            expr: "a+5",
            answer: 1,
        },
        test_case {
            expr: "!+5",
            answer: 1,
        },
        test_case {
            expr: "_a+5",
            answer: 1,
        },
        test_case {
            expr: "#a+5",
            answer: 1,
        },
        test_case {
            expr: "1^^5",
            answer: 3,
        },
     let test_cases = vec![
         test_case {
             input: "input1".to_string(),
             expected: "output1".to_string(),
         },
         test_case {
             input: "input2".to_string(),
             expected: "output2".to_string(),
         }
     ];
 ];
        let e = error.answer;
        let mut err = 0;
        let r = te_interp(expr, &mut err);
        assert!(err == e);
        assert!(r.is_nan());
        let n = te_compile(expr, 0, 0, &mut err);
        assert!(err == e);
        assert!(n.is_none());
        if err != e {
            println!("FAILED: {}", expr);
        }
        let k = te_interp(expr, &mut 0);
        assert!(k.is_nan());
    }
}


fn test_nans() {
    let nans = [
        "0/0",
        "1%0",
        "1%(1%0)",
        "(1%0)%1",
        "fac(-1)",
        "ncr(2, 4)",
        "ncr(-2, 4)",
        "ncr(2, -4)",
        "npr(2, 4)",
        "npr(-2, 4)",
        "npr(2, -4)",
    ];
    for expr in nans.iter() {
        let mut err = 0;
        let r = unsafe { te_interp(expr, &mut err) };
        assert_eq!(err, 0);
        assert!(r != r);
        let n = unsafe { te_compile(expr, std::ptr::null(), 0, &mut err) };
        assert!(!n.is_null());
        assert_eq!(err, 0);
        let c = unsafe { te_eval(n) };
        assert!(c != c);
        unsafe { te_free(n) };
    }
}

fn test_infs() {
    let infs = [
        "1/0",
        "log(0)",
        "pow(2,10000000)",
        "fac(300)",
        "ncr(300,100)",
        "ncr(300000,100)",
        "ncr(300000,100)*8",
        "npr(3,2)*ncr(300000,100)",
        "npr(100,90)",
        "npr(30,25)",
    ];
    for expr in &infs {
        let mut err = 0;
        let r = unsafe { te_interp(expr, &mut err) };
        assert!(err == 0);
        assert!(r == r + 1.0);
        let n = unsafe { te_compile(expr, ptr::null_mut(), ptr::null_mut(), &mut err) };
        assert!(!n.is_null());
        assert!(err == 0);
        let c = unsafe { te_eval(n) };
        assert!(c == c + 1.0);
        unsafe { te_free(n) };
    }
}

fn test_variables() {
    let mut x = 0.0;
    let mut y = 0.0;
    let mut test = 0.0;
    let lookup = [
        TeVariable {
            name: "x",
            address: &x,
        },
        TeVariable {
            name: "y",
            address: &y,
        },
        TeVariable {
            name: "te_st",
            address: &test,
        },
    ];
    let mut err: i32 = 0;
    let expr1_str = CString::new("cos x + sin y").unwrap();
    let expr1 = te_compile(expr1_str.as_ptr(), lookup.as_ptr(), 2, &mut err);
    assert!(!expr1.is_null());
    assert!(err == 0);
    let expr2_str = CString::new("x+x+x-y").unwrap();
    let expr2 = te_compile(expr2_str.as_ptr(), lookup.as_ptr(), 2, &mut err);
    assert!(!expr2.is_null());
    assert!(err == 0);
    let expr3_str = CString::new("x*y^3").unwrap();
    let expr3 = te_compile(expr3_str.as_ptr(), lookup.as_ptr(), 2, &mut err);
    assert!(!expr3.is_null());
    assert!(err == 0);
    let expr4_str = CString::new("te_st+5").unwrap();
    let expr4 = te_compile(expr4_str.as_ptr(), lookup.as_ptr(), 3, &mut err);
    assert!(!expr4.is_null());
    assert!(err == 0);
    for y_val in 2..3 {
        y = y_val as f64;
        for x_val in 0..5 {
            x = x_val as f64;
            let mut ev: f64;
            ev = te_eval(expr1);
            assert_float_equal(ev, x.cos() + y.sin());
            ev = te_eval(expr2);
            assert_float_equal(ev, x + x + x - y);
            ev = te_eval(expr3);
            assert_float_equal(ev, x * y * y * y);
            test = x;
            ev = te_eval(expr4);
            assert_float_equal(ev, x + 5.0);
        }
    }
    te_free(expr1);
    te_free(expr2);
    te_free(expr3);
    te_free(expr4);
    let expr5_str = CString::new("xx*y^3").unwrap();
    let expr5 = te_compile(expr5_str.as_ptr(), lookup.as_ptr(), 2, &mut err);
    assert!(expr5.is_null());
    assert!(err != 0);
    let expr6_str = CString::new("tes").unwrap();
    let expr6 = te_compile(expr6_str.as_ptr(), lookup.as_ptr(), 3, &mut err);
    assert!(expr6.is_null());
    assert!(err != 0);
    let expr7_str = CString::new("sinn x").unwrap();
    let expr7 = te_compile(expr7_str.as_ptr(), lookup.as_ptr(), 2, &mut err);
    assert!(expr7.is_null());
    assert!(err != 0);
    let expr8_str = CString::new("si x").unwrap();
    let expr8 = te_compile(expr8_str.as_ptr(), lookup.as_ptr(), 2, &mut err);
    assert!(expr8.is_null());
    assert!(err != 0);
}

fn test_functions() {
    let mut x: f64 = 0.0;
    let mut y: f64 = 0.0;
    let lookup = [
        TeVariable {
            name: CString::new("x").unwrap().as_ptr(),
            address: &x,
        },
        TeVariable {
            name: CString::new("y").unwrap().as_ptr(),
            address: &y,
        },
    ];
    for i in (-25..25).step_by(1) {
        x = i as f64 / 5.0;
        cross_check(CString::new("abs x").unwrap().as_ptr(), abs(x));
        cross_check(CString::new("acos x").unwrap().as_ptr(), acos(x));
        cross_check(CString::new("asin x").unwrap().as_ptr(), asin(x));
        cross_check(CString::new("atan x").unwrap().as_ptr(), atan(x));
        cross_check(CString::new("ceil x").unwrap().as_ptr(), ceil(x));
        cross_check(CString::new("cos x").unwrap().as_ptr(), cos(x));
        cross_check(CString::new("cosh x").unwrap().as_ptr(), cosh(x));
        cross_check(CString::new("exp x").unwrap().as_ptr(), exp(x));
        cross_check(CString::new("floor x").unwrap().as_ptr(), floor(x));
        cross_check(CString::new("ln x").unwrap().as_ptr(), log(x));
        cross_check(CString::new("log10 x").unwrap().as_ptr(), log10(x));
        cross_check(CString::new("sin x").unwrap().as_ptr(), sin(x));
        cross_check(CString::new("sinh x").unwrap().as_ptr(), sinh(x));
        cross_check(CString::new("sqrt x").unwrap().as_ptr(), sqrt(x));
        cross_check(CString::new("tan x").unwrap().as_ptr(), tan(x));
        cross_check(CString::new("tanh x").unwrap().as_ptr(), tanh(x));
        for j in (-10..10).step_by(1) {
            y = j as f64 / 5.0;
            if abs(x) < 0.01 {
                break;
            }
            cross_check(CString::new("atan2(x,y)").unwrap().as_ptr(), atan2(x, y));
            cross_check(CString::new("pow(x,y)").unwrap().as_ptr(), pow(x, y));
        }
    }
}

fn sum0() -> f64 {
    6.0
}

fn sum1(a: f64) -> f64 {
    a * 2.0
}

fn sum2(a: f64, b: f64) -> f64 {
    a + b
}

fn sum3(a: f64, b: f64, c: f64) -> f64 {
    a + b + c
}

fn sum4(a: f64, b: f64, c: f64, d: f64) -> f64 {
    a + b + c + d
}

fn sum5(a: f64, b: f64, c: f64, d: f64, e: f64) -> f64 {
    a + b + c + d + e
}

fn sum6(a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) -> f64 {
    a + b + c + d + e + f
}

fn sum7(a: f64, b: f64, c: f64, d: f64, e: f64, f: f64, g: f64) -> f64 {
    a + b + c + d + e + f + g
}

fn test_dynamic() {
    static mut X: f64 = 0.0;
    static mut F: f64 = 0.0;
    let lookup: &[TeVariable] = &[
        TeVariable {
            name: "x",
            address: unsafe { &mut X },
            fn_ptr: None,
        },
        TeVariable {
            name: "f",
            address: unsafe { &mut F },
            fn_ptr: None,
        },
        TeVariable {
            name: "sum0",
            address: std::ptr::null_mut(),
            fn_ptr: Some(sum0),
        },
        TeVariable {
            name: "sum1",
            address: std::ptr::null_mut(),
            fn_ptr: Some(sum1),
        },
        TeVariable {
            name: "sum2",
            address: std::ptr::null_mut(),
            fn_ptr: Some(sum2),
        },
        TeVariable {
            name: "sum3",
            address: std::ptr::null_mut(),
            fn_ptr: Some(sum3),
        },
        TeVariable {
            name: "sum4",
            address: std::ptr::null_mut(),
            fn_ptr: Some(sum4),
        },
        TeVariable {
            name: "sum5",
            address: std::ptr::null_mut(),
            fn_ptr: Some(sum5),
        },
        TeVariable {
            name: "sum6",
            address: std::ptr::null_mut(),
            fn_ptr: Some(sum6),
        },
        TeVariable {
            name: "sum7",
            address: std::ptr::null_mut(),
            fn_ptr: Some(sum7),
        },
    ];
    let cases: &[TestCase] = &[
        TestCase {
            expr: "x",
            answer: 2.0,
        },
        TestCase {
            expr: "f+x",
            answer: 7.0,
        },
        TestCase {
            expr: "x+x",
            answer: 4.0,
        },
        TestCase {
            expr: "x+f",
            answer: 7.0,
        },
        TestCase {
            expr: "f+f",
            answer: 10.0,
        },
        TestCase {
            expr: "f+sum0",
            answer: 11.0,
        },
        TestCase {
            expr: "sum0+sum0",
            answer: 12.0,
        },
        TestCase {
            expr: "sum0()+sum0",
            answer: 12.0,
        },
        TestCase {
            expr: "sum0+sum0()",
            answer: 12.0,
        },
        TestCase {
            expr: "sum0()+(0)+sum0()",
            answer: 12.0,
        },
        TestCase {
            expr: "sum1 sum0",
            answer: 12.0,
        },
        TestCase {
            expr: "sum1(sum0)",
            answer: 12.0,
        },
        TestCase {
            expr: "sum1 f",
            answer: 10.0,
        },
        TestCase {
            expr: "sum1 x",
            answer: 4.0,
        },
        TestCase {
            expr: "sum2 (sum0, x)",
            answer: 8.0,
        },
        TestCase {
            expr: "sum3 (sum0, x, 2)",
            answer: 10.0,
        },
        TestCase {
            expr: "sum2(2,3)",
            answer: 5.0,
        },
        TestCase {
            expr: "sum3(2,3,4)",
            answer: 9.0,
        },
        TestCase {
            expr: "sum4(2,3,4,5)",
            answer: 14.0,
        },
        TestCase {
            expr: "sum5(2,3,4,5,6)",
            answer: 20.0,
        },
        TestCase {
            expr: "sum6(2,3,4,5,6,7)",
            answer: 27.0,
        },
        TestCase {
            expr: "sum7(2,3,4,5,6,7,8)",
            answer: 35.0,
        },
    ];
    unsafe {
        X = 2.0;
        F = 5.0;
    }
    for case in cases {
        let expr = case.expr;
        let answer = case.answer;
        let err = 0;
        let ex = te_compile(expr, lookup, &mut err).expect("Compilation failed");
        assert!((te_eval(&ex) - answer).abs() < 1e-9);
        te_free(ex);
    }
}

fn clo0(context: Option<&mut f64>) -> f64 {
    if let Some(ctx) = context {
        *ctx + 6.0
    } else {
        6.0
    }
}

fn clo1(context: *mut std::ffi::c_void, a: f64) -> f64 {
    if !context.is_null() {
        unsafe { *(context as *mut f64) + a * 2.0 }
    } else {
        a * 2.0
    }
}

fn clo2(context: *mut std::ffi::c_void, a: f64, b: f64) -> f64 {
    if !context.is_null() {
        unsafe { *(context as *mut f64) + a + b }
    } else {
        a + b
    }
}

fn cell(context: &mut [f64], a: f64) -> f64 {
    context[a as usize]
}

fn test_closure() {
    let mut extra: f64;
    let c: [f64; 5] = [5.0, 6.0, 7.0, 8.0, 9.0];
    let lookup: [TeVariable; 4] = [
        TeVariable {
            name: "c0",
            function: clo0,
            arity: 0,
            context: &extra as *const f64,
        },
        TeVariable {
            name: "c1",
            function: clo1,
            arity: 1,
            context: &extra as *const f64,
        },
        TeVariable {
            name: "c2",
            function: clo2,
            arity: 2,
            context: &extra as *const f64,
        },
        TeVariable {
            name: "cell",
            function: cell,
            arity: 1,
            context: c.as_ptr(),
        },
    ];
    let cases: [TestCase; 3] = [
        TestCase {
            expr: "c0",
            answer: 6.0,
        },
        TestCase {
            expr: "c1 4",
            answer: 8.0,
        },
        TestCase {
            expr: "c2 (10, 20)",
            answer: 30.0,
        },
    ];
    for case in &cases {
        let expr = case.expr;
        let answer = case.answer;
        let mut err = 0;
        let ex = unsafe { te_compile(expr, lookup.as_ptr(), lookup.len() as i32, &mut err) };
        assert!(!ex.is_null());
        extra = 0.0;
        assert_float_equal(unsafe { te_eval(ex) }, answer + extra);
        extra = 10.0;
        assert_float_equal(unsafe { te_eval(ex) }, answer + extra);
        unsafe { te_free(ex) };
    }
    let cases2: [TestCase; 4] = [
        TestCase {
            expr: "cell 0",
            answer: 5.0,
        },
        TestCase {
            expr: "cell 1",
            answer: 6.0,
        },
        TestCase {
            expr: "cell 0 + cell 1",
            answer: 11.0,
        },
        TestCase {
            expr: "cell 1 * cell 3 + cell 4",
            answer: 57.0,
        },
    ];
    for case in &cases2 {
        let expr = case.expr;
        let answer = case.answer;
        let mut err = 0;
        let ex = unsafe { te_compile(expr, lookup.as_ptr(), lookup.len() as i32, &mut err) };
        assert!(!ex.is_null());
        assert_float_equal(unsafe { te_eval(ex) }, answer);
        unsafe { te_free(ex) };
    }
}

fn test_optimize() {
    for case in &CASES {
        let expr = CString::new(case.expr).unwrap();
        let expr_ptr = expr.as_ptr();
        let mut err: libc::c_int = 0;
        unsafe {
            let ex = te_compile(expr_ptr, ptr::null_mut(), 0, &mut err);
            assert!(!ex.is_null());
            assert_float_equal((*ex).value, case.answer);
            assert_float_equal(te_eval(ex), case.answer);
            te_free(ex);
        }
    }
}

fn test_pow() {
    #[cfg(TE_POW_FROM_RIGHT)]
    let cases = [
        TestEqu {
            expr1: "2^3^4",
            expr2: "2^(3^4)",
        },
        TestEqu {
            expr1: "-2^2",
            expr2: "-(2^2)",
        },
        TestEqu {
            expr1: "--2^2",
            expr2: "(2^2)",
        },
        TestEqu {
            expr1: "---2^2",
            expr2: "-(2^2)",
        },
        TestEqu {
            expr1: "-(2*1)^2",
            expr2: "-(2^2)",
        },
        TestEqu {
            expr1: "-2^2",
            expr2: "-4",
        },
        TestEqu {
            expr1: "2^1.1^1.2^1.3",
            expr2: "2^(1.1^(1.2^1.3))",
        },
        TestEqu {
            expr1: "-a^b",
            expr2: "-(a^b)",
        },
        TestEqu {
            expr1: "-a^-b",
            expr2: "-(a^-b)",
        },
        TestEqu {
            expr1: "1^0",
            expr2: "1",
        },
        TestEqu {
            expr1: "(1)^0",
            expr2: "1",
        },
        TestEqu {
            expr1: "-(2)^2",
            expr2: "-(2^2)",
        },
    ];
    #[cfg(not(TE_POW_FROM_RIGHT))]
    let cases = [
        TestEqu {
            expr1: "2^3^4",
            expr2: "(2^3)^4",
        },
        TestEqu {
            expr1: "-2^2",
            expr2: "(-2)^2",
        },
        TestEqu {
            expr1: "(-2)^2",
            expr2: "4",
        },
        TestEqu {
            expr1: "--2^2",
            expr2: "2^2",
        },
        TestEqu {
            expr1: "---2^2",
            expr2: "(-2)^2",
        },
        TestEqu {
            expr1: "-2^2",
            expr2: "4",
        },
        TestEqu {
            expr1: "2^1.1^1.2^1.3",
            expr2: "((2^1.1)^1.2)^1.3",
        },
        TestEqu {
            expr1: "-a^b",
            expr2: "(-a)^b",
        },
        TestEqu {
            expr1: "-a^-b",
            expr2: "(-a)^(-b)",
        },
        TestEqu {
            expr1: "1^0",
            expr2: "1",
        },
        TestEqu {
            expr1: "(1)^0",
            expr2: "1",
        },
        TestEqu {
            expr1: "(-1)^0",
            expr2: "1",
        },
        TestEqu {
            expr1: "(-5)^0",
            expr2: "1",
        },
        TestEqu {
            expr1: "-2^-3^-4",
            expr2: "((-2)^(-3))^(-4)",
        },
    ];
    let a = 2.0;
    let b = 3.0;
    let lookup = [("a", &a as *const f64), ("b", &b as *const f64)];
    for case in &cases {
        let expr1 = CString::new(case.expr1).unwrap();
        let expr2 = CString::new(case.expr2).unwrap();
        unsafe {
            let ex1 = te_compile(
                expr1.as_ptr(),
                lookup.as_ptr() as *const _,
                lookup.len() as i32,
                null_mut(),
            );
            let ex2 = te_compile(
                expr2.as_ptr(),
                lookup.as_ptr() as *const _,
                lookup.len() as i32,
                null_mut(),
            );
            assert!(!ex1.is_null());
            assert!(!ex2.is_null());
            let r1 = te_eval(ex1);
            let r2 = te_eval(ex2);
            assert!((r1 - r2).abs() < std::f64::EPSILON);
            te_free(ex1);
            te_free(ex2);
        }
    }
}

fn test_combinatorics() {
    let cases = [
        TestCase {
            expr: "fac(0)",
            answer: 1.0,
        },
        TestCase {
            expr: "fac(0.2)",
            answer: 1.0,
        },
        TestCase {
            expr: "fac(1)",
            answer: 1.0,
        },
        TestCase {
            expr: "fac(2)",
            answer: 2.0,
        },
        TestCase {
            expr: "fac(3)",
            answer: 6.0,
        },
        TestCase {
            expr: "fac(4.8)",
            answer: 24.0,
        },
        TestCase {
            expr: "fac(10)",
            answer: 3628800.0,
        },
        TestCase {
            expr: "ncr(0,0)",
            answer: 1.0,
        },
        TestCase {
            expr: "ncr(10,1)",
            answer: 10.0,
        },
        TestCase {
            expr: "ncr(10,0)",
            answer: 1.0,
        },
        TestCase {
            expr: "ncr(10,10)",
            answer: 1.0,
        },
        TestCase {
            expr: "ncr(16,7)",
            answer: 11440.0,
        },
        TestCase {
            expr: "ncr(16,9)",
            answer: 11440.0,
        },
        TestCase {
            expr: "ncr(100,95)",
            answer: 75287520.0,
        },
        TestCase {
            expr: "npr(0,0)",
            answer: 1.0,
        },
        TestCase {
            expr: "npr(10,1)",
            answer: 10.0,
        },
        TestCase {
            expr: "npr(10,0)",
            answer: 1.0,
        },
        TestCase {
            expr: "npr(10,10)",
            answer: 3628800.0,
        },
        TestCase {
            expr: "npr(20,5)",
            answer: 1860480.0,
        },
        TestCase {
            expr: "npr(100,4)",
            answer: 94109400.0,
        },
    ];
     for case in cases.iter() {
         let expr = case.expr;
         let answer = case.answer;
         let mut err = false;
         let ev = te_interp(expr, &mut err);
         assert!(!err);
         assert_float_equal(ev, answer);
         if err {
             println!("FAILED: {} ({})", expr, err as i32);
         }
     }
}

fn main() {
    run_tests(tests);
    println!("num_assert: {}", num_assert);
}

